package com.bolt.auth.security.auth.server.config;


import com.bolt.auth.security.auth.server.component.CustomAuthenticationProvider;
import com.bolt.auth.security.common.annotation.IgnoreFilter;
import com.bolt.auth.security.common.constant.SecurityConstants;
import com.bolt.auth.security.common.properties.FilterIgnoreProperties;
import com.bolt.common.codec.Base64Encoder;
import com.bolt.common.utils.SpringContextUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.bootstrap.encrypt.KeyProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;


@Configuration
@EnableConfigurationProperties(FilterIgnoreProperties.class)
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired(required = false)
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private UserDetailsService userDetailsService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Resource
    private LogoutHandler oauthLogoutHandler;

    @Autowired
    private LogoutSuccessHandler customLogoutSuccessHandler;

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private FilterIgnoreProperties filterIgnoreProperties;

    @Bean("keyProp")
    public KeyProperties keyProperties() {
        return new KeyProperties();
    }

    @Resource(name = "keyProp")
    private KeyProperties keyProperties;

    /**
     * 认证管理
     *
     * @return 认证管理对象
     * @throws Exception 认证异常信息
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }


    /**
     * 全局用户信息
     */
    @Autowired
    public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
        KeyPair keyPair = new KeyStoreKeyFactory
                (keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray())
                .getKeyPair(keyProperties.getKeyStore().getAlias());

        PrivateKey privateKey = keyPair.getPrivate();
        String key = Base64Encoder.encode(privateKey.getEncoded());
        auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService, passwordEncoder, key));
    }


    @Override
    public void configure(WebSecurity web) throws Exception {

        web.ignoring().antMatchers(HttpMethod.GET,
                "/*.html",
                "/swagger-resources",
                "/v2/api-docs-ext",
                "/v2/api-docs",
                "/doc.html",
                "/**/*.html",
                "/**/*.css",
                "/**/*.js",
                "/webSocket/**"
        );
        web.ignoring().antMatchers("/captcha/**");
        web.ignoring().antMatchers(filterIgnoreProperties.getIgnored());
        ignoreFilter(web);
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .anyRequest()
                .authenticated()//; // 都需要身份认证
                .and()
                .logout()
                .logoutUrl(SecurityConstants.LOGOUT_URL)
                .logoutSuccessHandler(customLogoutSuccessHandler)
                .addLogoutHandler(oauthLogoutHandler)
                .and()
                .csrf().disable()
                // 解决不允许显示在iframe的问题
                .headers().frameOptions().disable().cacheControl();

        http.formLogin()
                .loginPage(SecurityConstants.LOGIN_PAGE)
                .loginProcessingUrl(SecurityConstants.OAUTH_LOGIN_PRO_URL)
                .successHandler(authenticationSuccessHandler);
        // 基于密码 等模式可以无session,不支持授权码模式

        if (authenticationEntryPoint != null) {
            http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        } else {
            // 授权码模式单独处理，需要session的支持，此模式可以支持所有oauth2的认证
            http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        }
    }

    private void ignoreFilter(WebSecurity web) {
        RequestMappingHandlerMapping requestMappingHandlerMapping =
                (RequestMappingHandlerMapping) SpringContextUtil.getBean("requestMappingHandlerMapping");
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            IgnoreFilter ignoreFilter = handlerMethod.getMethodAnnotation(IgnoreFilter.class);
            if (null != ignoreFilter) {
                List<RequestMethod> requestMethods = new ArrayList<>(infoEntry.getKey().getMethodsCondition().getMethods());
                HttpMethod request = HttpMethod.resolve(requestMethods.get(0).name());
                String[] ignoreUrl = infoEntry.getKey().getPatternsCondition().getPatterns().toArray(new String[0]);
                switch (Objects.requireNonNull(request)) {
                    case GET:
                        web.ignoring().antMatchers(HttpMethod.GET, ignoreUrl);
                        break;
                    case POST:
                        web.ignoring().antMatchers(HttpMethod.POST, ignoreUrl);
                        break;
                    case PUT:
                        web.ignoring().antMatchers(HttpMethod.PUT, ignoreUrl);
                        break;
                    case PATCH:
                        web.ignoring().antMatchers(HttpMethod.PATCH, ignoreUrl);
                        break;
                    case DELETE:
                        web.ignoring().antMatchers(HttpMethod.DELETE, ignoreUrl);
                        break;
                    default:
                        web.ignoring().antMatchers(ignoreUrl);
                        break;
                }
            }
        }
    }


}

 